package com.yupi.springbootinit.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.*;
import com.yupi.springbootinit.model.dto.processbatch.ProcessBatchScheduleUpdateRequest;
import com.yupi.springbootinit.model.dto.processbatch.ProcessBatchStepDurationDTO;
import com.yupi.springbootinit.model.dto.processbatch.ProcessBatchUpdateRequest;
import com.yupi.springbootinit.model.entity.*;
import com.yupi.springbootinit.model.vo.PMCTaskDetailVO;
import com.yupi.springbootinit.model.vo.pmc.PmcGroupedBatchesVO;
import com.yupi.springbootinit.model.vo.pmc.ProcessBatchDetailVO;
import com.yupi.springbootinit.model.vo.pmc.ProcessBatchProgressVO;
import com.yupi.springbootinit.service.BatchCodeGeneratorService;
import com.yupi.springbootinit.service.OutboxService;
import com.yupi.springbootinit.service.ProcessBatchService;
import com.yupi.springbootinit.service.WorkflowTaskService;
import com.yupi.springbootinit.service.workflow.WorkflowTaskCreationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.Collator;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static com.yupi.springbootinit.constant.ProcessBatchConstants.Status.*;

@Slf4j
@Service
public class ProcessBatchServiceImpl implements ProcessBatchService {

    @Resource
    private ProcessBatchMapper processBatchMapper;
    @Resource
    private ProcessBatchStepMapper processBatchStepMapper;

    @Resource
    private ProcessSheetBatchMapper processSheetBatchMapper;

    @Resource
    private WorkflowTaskCreationService workflowTaskCreationService;

    @Resource
    private ProcessSheetMapper processSheetMapper;

    @Resource
    private ProcessSheetProductMapper processSheetProductMapper;

    @Resource
    private ProcessSheetBatchProductMapper processSheetBatchProductMapper;

    @Resource
    private ProcessSheetStepMapper processSheetStepMapper;

    @Resource
    private OperationMapper operationMapper;

    @Resource
    private DeviceInfoMapper deviceInfoMapper;

    @Resource
    private BatchCodeGeneratorService batchCodeGeneratorService;

    @Resource
    private ProductionOperationRecordMapper productionOperationRecordMapper;
    @Resource
    private BatchScheduleHistoryMapper batchScheduleHistoryMapper;

    @Resource
    private SalesOrderItemMapper salesOrderItemMapper;

    @Resource
    private SalesOrderMapper salesOrderMapper;

    @Resource
    private OutboxService outboxService;
    @Resource
    private WorkflowTaskMapper workflowTaskMapper;
    @Resource
    private WorkflowTaskService workflowTaskService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ProcessBatch> generateBatches(Long processSheetId, Integer total, List<String> batchCodes, String batchCodePrefix, List<Integer> batchQuantities, Integer defaultQuantityPerBatch, Long userId, String userName) {
        if (processSheetId == null || processSheetId <= 0 || total == null || total <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不合法");
        }

        ProcessSheet sheet = processSheetMapper.selectById(processSheetId);
        if (sheet == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "工艺单不存在");
        }

        // 【修改】前置校验设备信息，确保后续PMC提交时能生成编号
        if (sheet.getDeviceId() == null || StringUtils.isBlank(sheet.getDeviceCode())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "工艺单未设置生产设备，无法创建批次，请先完善工艺单设备信息");
        }

        Long existCnt = processBatchMapper.selectCount(new QueryWrapper<ProcessBatch>().eq("process_sheet_id", processSheetId).eq("is_deleted", 0));
        if (existCnt != null && existCnt > 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "该工艺单已有有效版次，请先删除后再重新设置");
        }

        // 准备订单和产品信息
        Integer sourceType = sheet.getSourceType();
        Long sourceId = sheet.getSourceId();
        String orderType = (sourceType != null && sourceType == 1) ? "SALES" : "RDO";
        String orderNumber = sheet.getSourceNo();
        String productName = sheet.getProductName();

        // 尝试从订单明细获取更准确的产品名
        if (sourceId != null) {
            SalesOrder so = salesOrderMapper.selectById(sourceId);
            if (so != null) {
                orderNumber = so.getOrderNumber();
                try {
                    List<SalesOrderItem> orderItems = salesOrderItemMapper.selectList(new LambdaQueryWrapper<SalesOrderItem>().eq(SalesOrderItem::getOrderId, so.getId()).eq(SalesOrderItem::getIsDeleted, 0).orderByAsc(SalesOrderItem::getSortOrder).last("LIMIT 1"));
                    if (orderItems != null && !orderItems.isEmpty()) {
                        productName = orderItems.get(0).getProductName();
                    }
                } catch (Exception e) {
                    log.warn("查询订单明细失败", e);
                }
            }
        }

        // 获取工序模板
        List<ProcessSheetStep> templateSteps = processSheetStepMapper.selectList(new QueryWrapper<ProcessSheetStep>().eq("process_sheet_id", processSheetId).eq("is_deleted", 0).orderByAsc("sequence_no", "id"));

        LocalDateTime now = LocalDateTime.now();
        List<ProcessBatch> result = new ArrayList<>();

        // 处理数量分配
        List<Integer> quantities = new ArrayList<>();
        if (batchQuantities != null && batchQuantities.size() == total) {
            quantities.addAll(batchQuantities);
        } else {
            int qty = (defaultQuantityPerBatch != null && defaultQuantityPerBatch > 0) ? defaultQuantityPerBatch : 1;
            for (int i = 0; i < total; i++) quantities.add(qty);
        }

        // 循环创建批次
        for (int i = 0; i < total; i++) {
            Integer quantity = i < quantities.size() ? quantities.get(i) : 1;

            ProcessBatch pb = new ProcessBatch();
            pb.setProcessSheetId(processSheetId);
            pb.setSourceType(sourceType);
            pb.setSourceId(sourceId);
            pb.setSourceNo(sheet.getSourceNo());
            pb.setOrderType(orderType);
            pb.setOrderNumber(orderNumber);
            pb.setProductName(productName);

            // 【修改】工程部创建阶段不生成批次号，留空等待PMC提交时生成
            pb.setBatchCode(null);

            // 【修改】必须填充设备信息
            pb.setDeviceId(sheet.getDeviceId());
            pb.setDeviceCode(sheet.getDeviceCode());
            pb.setDeviceName(sheet.getDeviceName());

            pb.setQuantity(quantity);
            pb.setStatus(WAITING_RELEASE);
            pb.setCreatedBy(userId);
            pb.setCreatedByName(userName);
            pb.setCreateTime(now);
            pb.setUpdateTime(now);
            pb.setIsDeleted(0);
            processBatchMapper.insert(pb);

            // 创建PMC发布待办
            try {
                Long parentTaskId = null;
                WorkflowTask parentTask = workflowTaskMapper.selectOne(new QueryWrapper<WorkflowTask>().eq("bizType", "SALES_ORDER").eq("bizId", sourceId).eq("taskType", "CREATE_SCHEDULE").eq("isDelete", 0).last("LIMIT 1"));
                if (parentTask != null) parentTaskId = parentTask.getId();

                // 【修改】传入提示文本，因为此时还没有批次号
                workflowTaskService.createPublishBatchTaskForBatch(pb.getId(), orderNumber, "待生成编号", parentTaskId, userId, userName);
            } catch (Exception e) {
                log.warn("创建发布版次子待办失败，batchId={}", pb.getId(), e);
            }

            // 复制工序步骤
            if (templateSteps != null) {
                for (ProcessSheetStep s : templateSteps) {
                    ProcessBatchStep bs = new ProcessBatchStep();
                    bs.setBatchId(pb.getId());
                    bs.setProcessSheetStepId(s.getId());
                    bs.setSequenceNo(s.getSequenceNo());
                    bs.setOperationId(s.getOperationId());
                    bs.setOperationCode(s.getOperationCode());
                    bs.setOperationName(s.getOperationName());
                    // 复制检验要求
                    bs.setNeedInspection(s.getIsInspectionRequired() != null ? s.getIsInspectionRequired() : 0);
                    bs.setProcessRequirement(s.getProcessRequirement());
                    bs.setInspectionMethod(s.getInspectionMethod());
                    bs.setInspectionCriteria(s.getInspectionCriteria());
                    bs.setStatus("PLANNED");
                    bs.setCreateTime(now);
                    bs.setUpdateTime(now);
                    bs.setIsDeleted(0);
                    processBatchStepMapper.insert(bs);
                }
            }
            result.add(pb);
        }

        return result;
    }

    // 本地辅助：0->A, 25->Z, 26->AA ...
    private String toAlphaCode(int index) {
        StringBuilder sb = new StringBuilder();
        int i = index;
        do {
            int rem = i % 26;
            sb.insert(0, (char) ('A' + rem));
            i = i / 26 - 1;
        } while (i >= 0);
        return sb.toString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessBatch autoSchedule(Long batchId, LocalDateTime planStart, Long userId, String userName) {
        return autoSchedule(batchId, planStart, null, userId, userName);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessBatch autoSchedule(Long batchId, LocalDateTime planStart, List<ProcessBatchStepDurationDTO> stepDurations, Long userId, String userName) {
        if (batchId == null || batchId <= 0 || planStart == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不合法");
        }
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "版次不存在");
        }

        // 如果提供了工序时长，先更新工序时长
        if (stepDurations != null && !stepDurations.isEmpty()) {
            log.info("自动排程前先更新工序时长，共{}个工序", stepDurations.size());
            for (ProcessBatchStepDurationDTO dto : stepDurations) {
                log.debug("将更新工序sequenceNo={}的时长为{}分钟", dto.getSequenceNo(), dto.getExpectedDurationMinutes());
            }
            updateStepDurations(batchId, stepDurations, userId, userName);
        } else {
            log.info("自动排程未提供工序时长数据，将使用数据库中已有的时长或默认值");
        }

        // 重新查询最新的工序数据，确保获取到用户最新修改的时长
        List<ProcessBatchStep> steps = processBatchStepMapper.selectList(new LambdaQueryWrapper<ProcessBatchStep>().eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0).orderByAsc(ProcessBatchStep::getSequenceNo, ProcessBatchStep::getId));
        if (steps == null || steps.isEmpty()) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "版次无工序步骤，无法排程");
        }

        log.info("开始自动排程，共{}个工序步骤", steps.size());
        for (ProcessBatchStep step : steps) {
            log.debug("工序[{}] sequenceNo={}, 当前保存的时长={}分钟", step.getOperationName(), step.getSequenceNo(), step.getExpectedDurationMinutes());
        }

        // 预取工序标准工时（小时）
        java.util.Set<Long> opIds = new java.util.HashSet<>();
        for (ProcessBatchStep s : steps) {
            if (s.getOperationId() != null) opIds.add(s.getOperationId());
        }
        Map<Long, Operation> opMap = Collections.emptyMap();
        if (!opIds.isEmpty()) {
            List<Operation> ops = operationMapper.selectBatchIds(new ArrayList<>(opIds));
            if (ops != null && !ops.isEmpty()) {
                opMap = ops.stream().collect(Collectors.toMap(com.yupi.springbootinit.model.entity.Operation::getId, o -> o));
            }
        }

        LocalDateTime cursor = planStart;
        LocalDateTime first = null;
        LocalDateTime last = null;
        LocalDateTime now = LocalDateTime.now();

        for (ProcessBatchStep s : steps) {
            int minutes = 0;
            // 优化后的三级优先级策略：
            // 1) 数据库中已保存的自定义工序时长（expected_duration_minutes）
            // 2) 工序库标准工时 
            // 3) 默认60分钟
            if (s.getExpectedDurationMinutes() != null && s.getExpectedDurationMinutes() > 0) {
                minutes = s.getExpectedDurationMinutes();
                log.debug("工序[{}]使用数据库中保存的自定义时长: {}分钟", s.getOperationName(), minutes);
            } else {
                Operation op = s.getOperationId() != null ? opMap.get(s.getOperationId()) : null;
                if (op != null && op.getStandardTime() != null) {
                    BigDecimal mins = op.getStandardTime().multiply(new BigDecimal(60)).setScale(0, java.math.RoundingMode.UP);
                    minutes = mins.intValue();
                    log.debug("工序[{}]使用标准工时: {}分钟", s.getOperationName(), minutes);
                } else {
                    minutes = 60; // 兜底60分钟
                    log.warn("工序[{}]缺少时长设置，使用默认60分钟", s.getOperationName());
                }
            }
            if (minutes <= 0) {
                minutes = 60; // 兜底60分钟
                log.warn("工序[{}]时长设置无效，使用默认60分钟", s.getOperationName());
            }

            LocalDateTime start = cursor;
            LocalDateTime end = cursor.plusMinutes(minutes);
            if (first == null) first = start;
            last = end;

            // 只更新计划时间，不覆盖用户设置的expected_duration_minutes
            UpdateWrapper<ProcessBatchStep> uw = new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<>();
            uw.eq("id", s.getId()).set("plan_start", start).set("plan_end", end).set("update_time", now);
            processBatchStepMapper.update(null, uw);

            log.debug("工序[{}]排程完成: 开始时间={}, 结束时间={}, 时长={}分钟", s.getOperationName(), start, end, minutes);

            cursor = end;
        }

        // 更新批次起止
        UpdateWrapper<ProcessBatch> ubw = new UpdateWrapper<>();
        ubw.eq("id", batchId).set("plan_start", first).set("plan_end", last).set("update_time", LocalDateTime.now());
        processBatchMapper.update(null, ubw);

        log.info("自动排程完成 - 批次ID:{}, 计划开始:{}, 计划结束:{}", batchId, first, last);

        // 返回更新后的批次信息
        ProcessBatch result = processBatchMapper.selectById(batchId);
        log.info("返回批次信息 - ID:{}, 状态:{}, 计划开始:{}, 计划结束:{}", result.getId(), result.getStatus(), result.getPlanStart(), result.getPlanEnd());

        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStepDurations(Long batchId, List<ProcessBatchStepDurationDTO> overrides, Long userId, String userName) {
        if (batchId == null || batchId <= 0 || overrides == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        log.info("开始更新批次{}的工序时长，共{}个工序", batchId, overrides.size());
        int updated = 0;
        for (ProcessBatchStepDurationDTO o : overrides) {
            if (o == null || o.getSequenceNo() == null || o.getExpectedDurationMinutes() == null) {
                log.warn("跳过无效的工序时长设置：sequenceNo={}, expectedDurationMinutes={}", o != null ? o.getSequenceNo() : null, o != null ? o.getExpectedDurationMinutes() : null);
                continue;
            }
            int finalMinutes = Math.max(1, o.getExpectedDurationMinutes());
            log.info("更新工序时长：sequenceNo={}, 原始时长={}分钟, 实际设置={}分钟", o.getSequenceNo(), o.getExpectedDurationMinutes(), finalMinutes);

            UpdateWrapper<ProcessBatchStep> uw = new UpdateWrapper<>();
            uw.eq("batch_id", batchId).eq("sequence_no", o.getSequenceNo()).eq("is_deleted", 0).set("expected_duration_minutes", finalMinutes).set("update_time", java.time.LocalDateTime.now());
            int rows = processBatchStepMapper.update(null, uw);
            if (rows > 0) {
                log.info("成功更新工序{}的时长为{}分钟", o.getSequenceNo(), finalMinutes);
            } else {
                log.warn("未能更新工序{}的时长，可能工序不存在", o.getSequenceNo());
            }
            updated += rows;
        }
        log.info("工序时长更新完成，共更新{}个工序", updated);
        return updated >= 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchBase(Long batchId, ProcessBatchUpdateRequest req, Long userId, String userName) {
        if (batchId == null || batchId <= 0 || req == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        ProcessBatch update = new ProcessBatch();
        update.setId(batchId);
        update.setPowderModel(req.getPowderModel());
        update.setPowderWeight(req.getPowderWeight());
        update.setCleaningDeviceId(req.getCleaningDeviceId());
        update.setCleaningDeviceName(req.getCleaningDeviceName());
        update.setRemark(req.getRemark());
        processBatchMapper.updateById(update);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateSchedule(Long batchId, ProcessBatchScheduleUpdateRequest req, Long userId, String userName) {
        if (batchId == null || batchId <= 0 || req == null || req.getPlanStart() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "版次不存在");
        }
        // 禁止对已完成/已取消的版次改期
        String st = batch.getStatus();
        if (COMPLETED.equals(st) || CANCELLED.equals(st)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "已完成/已取消的版次不允许改期");
        }
        // 原因必填
        if (req.getReason() == null || req.getReason().trim().isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "改期原因不能为空");
        }
        // 乐观锁版本必填
        if (req.getVersion() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "缺少版本号version，无法执行改期");
        }
        LocalDateTime oldStart = batch.getPlanStart();

        LocalDateTime oldEnd = batch.getPlanEnd();
        if (oldStart == null) {
            // 若未排程过，则转为自动排程
            autoSchedule(batchId, req.getPlanStart(), userId, userName);
            return true;
        }
        long deltaMinutes = Duration.between(oldStart, req.getPlanStart()).toMinutes();

        // 平移步骤
        List<ProcessBatchStep> steps = processBatchStepMapper.selectList(new LambdaQueryWrapper<ProcessBatchStep>().eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0).orderByAsc(ProcessBatchStep::getSequenceNo, ProcessBatchStep::getId));
        LocalDateTime now = java.time.LocalDateTime.now();
        for (ProcessBatchStep s : steps) {
            if (s.getPlanStart() != null) {
                LocalDateTime ns = s.getPlanStart().plusMinutes(deltaMinutes);
                LocalDateTime ne = s.getPlanEnd() != null ? s.getPlanEnd().plusMinutes(deltaMinutes) : null;
                UpdateWrapper<ProcessBatchStep> uw = new UpdateWrapper<>();
                uw.eq("id", s.getId()).set("plan_start", ns).set(ne != null, "plan_end", ne).set("update_time", now);
                processBatchStepMapper.update(null, uw);
            }
        }

        // 更新批次并写历史
        LocalDateTime newStart = req.getPlanStart();
        LocalDateTime newEnd = (oldEnd != null ? oldEnd.plusMinutes(deltaMinutes) : null);

        UpdateWrapper<ProcessBatch> ubw = new UpdateWrapper<>();
        ubw.eq("id", batchId).eq("version", req.getVersion()) // 乐观锁
                .set("plan_start", newStart).set(newEnd != null, "plan_end", newEnd).set("update_time", now).setSql("version = version + 1");
        int rows = processBatchMapper.update(null, ubw);
        if (rows == 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "版本冲突，请刷新后重试");
        }

        // 历史
        BatchScheduleHistory his = new BatchScheduleHistory();
        his.setBatchId(batchId);
        his.setBeforePlanStart(oldStart);
        his.setBeforePlanEnd(oldEnd);
        his.setAfterPlanStart(newStart);
        his.setAfterPlanEnd(newEnd);
        his.setReason(req.getReason());
        his.setOperatorId(userId);
        his.setOperatorName(userName);
        his.setChangedAt(now);
        batchScheduleHistoryMapper.insert(his);

        return rows > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean submit(Long batchId, Long userId, String userName) {
        if (batchId == null || batchId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "版次不存在");
        }

        if (!"WAITING_RELEASE".equals(batch.getStatus())) {
            return true;
        }

        if (batch.getPlanStart() == null || batch.getPlanEnd() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未完成排程，无法提交");
        }
        Long unfinished = processBatchStepMapper.selectCount(new LambdaQueryWrapper<ProcessBatchStep>().eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0).and(w -> w.isNull(ProcessBatchStep::getPlanStart).or().isNull(ProcessBatchStep::getPlanEnd)));
        if (unfinished > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "存在未排程的工序，无法提交");
        }

        // ========== 核心修改：生成编号逻辑 ==========
        if (StringUtils.isBlank(batch.getBatchCode()) || "待生成编号".equals(batch.getBatchCode())) {

            // 1. 生成流程卡/生产计划号
            String flowNumber = batchCodeGeneratorService.generateFlowNumber();

            // 2.1 产品代号：直接根据产品名称生成（拼音/数字规则）
            String productCodeForGen = batchCodeGeneratorService.generateProductCode(batch.getProductName());

            // 2.2 机台号
            String deviceCode = batch.getDeviceCode();
            if (StringUtils.isBlank(deviceCode)) {
                ProcessSheet sheet = processSheetMapper.selectById(batch.getProcessSheetId());
                if (sheet != null && StringUtils.isNotBlank(sheet.getDeviceCode())) {
                    deviceCode = sheet.getDeviceCode();
                } else {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "未设置生产机台号，无法生成批次号");
                }
            }

            // 2.3 版次序号
            Integer batchNo = 1;
            if (batch.getBatchPlanId() != null) {
                ProcessSheetBatch plan = processSheetBatchMapper.selectById(batch.getBatchPlanId());
                if (plan != null && plan.getBatchNo() != null) {
                    batchNo = plan.getBatchNo();
                }
            }

            if (batchNo == 1) {
                LambdaQueryWrapper<ProcessBatch> qw = new LambdaQueryWrapper<>();
                qw.eq(ProcessBatch::getProcessSheetId, batch.getProcessSheetId()).eq(ProcessBatch::getIsDeleted, 0).lt(ProcessBatch::getId, batchId).isNotNull(ProcessBatch::getBatchCode);
                Long count = processBatchMapper.selectCount(qw);
                batchNo = count != null ? count.intValue() + 1 : 1;
            }

            // 3. 生成批次号
            String batchCode = batchCodeGeneratorService.generateBatchCode(productCodeForGen, deviceCode, batchNo);

            // 4. 更新数据库
            UpdateWrapper<ProcessBatch> uw = new UpdateWrapper<>();
            uw.eq("id", batchId).set("batch_code", batchCode).set("flow_number", flowNumber).set("production_plan_number", flowNumber).set("device_code", deviceCode).set("update_time", LocalDateTime.now());
            processBatchMapper.update(null, uw);

            batch.setBatchCode(batchCode);
            batch.setFlowNumber(flowNumber);
            batch.setProductionPlanNumber(flowNumber);
        }

        // ========== 状态更新 ==========
        LocalDateTime now = LocalDateTime.now();
        UpdateWrapper<ProcessBatch> uwStatus = new UpdateWrapper<>();
        uwStatus.eq("id", batchId).eq("status", WAITING_RELEASE).set("status", RELEASED).set("publish_time", now).set("update_time", now).setSql("version = version + 1");

        boolean success = processBatchMapper.update(null, uwStatus) > 0;

        if (success) {
            workflowTaskCreationService.createProductionPrepTaskForBatch(batchId, batch.getOrderNumber(), batch.getProductName(), "PRODUCTION_PREP_CLEANING", userId, userName);

            try {
                workflowTaskService.completePublishBatchTaskForBatch(batchId, userId, userName, "发布完成");
            } catch (Exception e) {
                log.warn("完结任务失败", e);
            }

            // 父任务检查逻辑保持不变...
            try {
                Long remaining = processBatchMapper.selectCount(new QueryWrapper<ProcessBatch>().eq("process_sheet_id", batch.getProcessSheetId()).eq("is_deleted", 0).eq("status", "WAITING_RELEASE"));
                if (remaining != null && remaining == 0 && batch.getSourceId() != null) {
                    if (batch.getSourceType() == 1) {
                        workflowTaskService.completeScheduleTaskForSalesOrder(batch.getSourceId(), userId, userName, "全部版次发布完成");
                    } else if (batch.getSourceType() == 2) {
                        workflowTaskService.completeScheduleTaskForRdoOrder(batch.getSourceId(), userId, userName, "全部版次发布完成");
                    }
                }
            } catch (Exception e) {
                log.warn("完结父任务失败", e);
            }
        }
        return success;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteBatch(Long batchId, String reason, Long userId, String userName) {
        if (batchId == null || batchId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "批次ID不能为空");
        }
        if (reason == null || reason.trim().isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "删除原因不能为空");
        }

        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "批次不存在或已删除");
        }

        log.info("========== 开始删除批次 ==========");
        log.info("batchId={}, batchCode={}, reason={}, operator={}", batchId, batch.getBatchCode(), reason, userName);

        // ========== 1. 逻辑删除批次 ==========
        LocalDateTime now = LocalDateTime.now();
        UpdateWrapper<ProcessBatch> uw = new UpdateWrapper<>();
        uw.eq("id", batchId).eq("is_deleted", 0).set("is_deleted", 1).set("update_time", now).setSql("version = version + 1");

        int rows = processBatchMapper.update(null, uw);
        boolean success = rows > 0;

        if (!success) {
            log.error("❌ 批次删除失败: batchId={}, 可能已被删除", batchId);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "批次删除失败");
        }

        log.info("✅ 批次已软删除: batchId={}, batchCode={}", batchId, batch.getBatchCode());

        // ========== 2. 记录删除历史 ==========
        BatchScheduleHistory deleteHistory = new BatchScheduleHistory();
        deleteHistory.setBatchId(batchId);
        deleteHistory.setBeforePlanStart(batch.getPlanStart());
        deleteHistory.setBeforePlanEnd(batch.getPlanEnd());
        deleteHistory.setAfterPlanStart(null);
        deleteHistory.setAfterPlanEnd(null);
        deleteHistory.setReason("删除批次：" + reason.trim());
        deleteHistory.setOperatorId(userId);
        deleteHistory.setOperatorName(userName);
        deleteHistory.setChangedAt(now);
        batchScheduleHistoryMapper.insert(deleteHistory);

        log.info("✅ 删除历史已记录: batchId={}", batchId);

        // ========== 3. ✅ 查询并删除关联的任务 ==========
        try {
            List<WorkflowTask> relatedTasks = workflowTaskMapper.selectList(new LambdaQueryWrapper<WorkflowTask>().eq(WorkflowTask::getBizType, "PROCESS_BATCH").eq(WorkflowTask::getBizId, batchId).eq(WorkflowTask::getTaskType, "PUBLISH_BATCH").eq(WorkflowTask::getIsDelete, 0));

            if (relatedTasks != null && !relatedTasks.isEmpty()) {
                for (WorkflowTask task : relatedTasks) {
                    log.warn("⚠️ 删除批次关联的任务: taskId={}, taskTitle={}, taskStatus={}, claimedBy={}", task.getId(), task.getTaskTitle(), task.getTaskStatus(), task.getClaimedByName());

                    // 软删除任务
                    LambdaUpdateWrapper<WorkflowTask> taskUw = new LambdaUpdateWrapper<>();
                    taskUw.eq(WorkflowTask::getId, task.getId()).eq(WorkflowTask::getIsDelete, 0).set(WorkflowTask::getIsDelete, 1).set(WorkflowTask::getUpdateTime, now).set(WorkflowTask::getCompletionRemark, "批次已删除，原因：" + reason.trim());

                    int taskRows = workflowTaskMapper.update(null, taskUw);

                    if (taskRows > 0) {
                        log.info("✅ 任务已删除: taskId={}, taskTitle={}", task.getId(), task.getTaskTitle());
                    } else {
                        log.warn("⚠️ 任务删除失败: taskId={}", task.getId());
                    }
                }

                log.info("✅ 批次关联的任务已全部删除: batchId={}, 任务数量={}", batchId, relatedTasks.size());
            } else {
                log.info("批次无关联任务: batchId={}", batchId);
            }
        } catch (Exception e) {
            log.error("❌ 删除批次关联任务失败: batchId={}", batchId, e);
            // ✅ 不抛异常，避免影响批次删除
            // 任务删除失败不影响批次删除的主流程
        }

        // ========== 4. ✅ 检查工艺单是否还有剩余批次 ==========
        try {
            Long remainingCount = processBatchMapper.selectCount(new LambdaQueryWrapper<ProcessBatch>().eq(ProcessBatch::getProcessSheetId, batch.getProcessSheetId()).eq(ProcessBatch::getIsDeleted, 0));

            if (remainingCount == null || remainingCount == 0) {
                log.warn("⚠️ 该工艺单的所有批次都已被删除: processSheetId={}, 建议通知工程部重新设置批次", batch.getProcessSheetId());
            } else {
                log.info("工艺单剩余批次数: processSheetId={}, remainingCount={}", batch.getProcessSheetId(), remainingCount);
            }
        } catch (Exception e) {
            log.warn("检查剩余批次数量失败: processSheetId={}", batch.getProcessSheetId(), e);
        }

        // ========== 5. ✅ 检查是否需要更新父任务状态 ==========
        // 注意：删除批次不应该完结父任务，因为可能需要重新创建批次
        // 只有在所有批次都成功发布时才完结父任务（在submit方法中处理）

        log.info("========== 批次删除完成 ==========");
        log.info("batchId={}, batchCode={}, 关联任务已同步删除", batchId, batch.getBatchCode());

        return true;
    }

    @Override
    public List<ProcessBatch> listBatches(Long taskId, Long processSheetId, String orderNumber, String productName, String status) {
        // 如果传入了 taskId，先通过 taskId 找到 processSheetId
        if (taskId != null && taskId > 0) {
            WorkflowTask task = workflowTaskMapper.selectById(taskId);
            if (task != null) {
                if (processSheetId == null && "CREATE_SCHEDULE".equals(task.getTaskType())) {
                    Long orderId = task.getBizId();
                    ProcessSheet sheet = processSheetMapper.selectOne(new LambdaQueryWrapper<ProcessSheet>().eq(ProcessSheet::getSourceId, orderId).eq(ProcessSheet::getSourceType, 1).eq(ProcessSheet::getIsDeleted, 0).last("LIMIT 1"));
                    if (sheet != null) {
                        processSheetId = sheet.getId();
                    }
                }
            }
        }

        LambdaQueryWrapper<ProcessBatch> qw = new LambdaQueryWrapper<>();
        if (processSheetId != null && processSheetId > 0) {
            qw.eq(ProcessBatch::getProcessSheetId, processSheetId);
        }
        if (orderNumber != null && !orderNumber.isEmpty()) {
            qw.eq(ProcessBatch::getOrderNumber, orderNumber);
        }
        if (productName != null && !productName.isEmpty()) {
            qw.like(ProcessBatch::getProductName, productName);
        }
        if (status != null && !status.isEmpty()) {
            qw.eq(ProcessBatch::getStatus, status);
        }
        qw.eq(ProcessBatch::getIsDeleted, 0).orderByAsc(ProcessBatch::getOrderNumber, ProcessBatch::getBatchCode);
        return processBatchMapper.selectList(qw);
    }

    @Override
    public ProcessBatchDetailVO getBatchDetail(Long id) {
        ProcessBatch batch = processBatchMapper.selectById(id);
        if (batch == null || batch.getIsDeleted() == 1) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }

        ProcessBatchDetailVO vo = new ProcessBatchDetailVO();
        // 批次基础信息
        vo.setId(batch.getId());
        vo.setBatchCode(batch.getBatchCode());
        vo.setOrderNumber(batch.getOrderNumber());
        vo.setStatus(batch.getStatus());
        vo.setPlanStart(batch.getPlanStart());
        vo.setPlanEnd(batch.getPlanEnd());
        vo.setPublishTime(batch.getPublishTime());
        vo.setQuantity(batch.getQuantity());
        vo.setDeviceName(batch.getDeviceName());
        vo.setFlowNumber(batch.getFlowNumber());
        vo.setProductionPlanNumber(batch.getProductionPlanNumber());

        // ✅ 查询工艺单获取材料信息
        ProcessSheet sheet = processSheetMapper.selectById(batch.getProcessSheetId());
        if (sheet != null) {
            vo.setMaterialBrand(sheet.getMaterialBrand());
            vo.setMaterialSpec(sheet.getMaterialSpec());
            vo.setPowderSupplier(sheet.getPowderSupplier());
        }

        // ✅ 查询产品明细（优化版）
        if (batch.getBatchPlanId() != null) {
            List<ProcessSheetBatchProduct> batchProducts = processSheetBatchProductMapper.selectList(
                    new LambdaQueryWrapper<ProcessSheetBatchProduct>()
                            .eq(ProcessSheetBatchProduct::getBatchId, batch.getBatchPlanId())
                            .eq(ProcessSheetBatchProduct::getIsDeleted, 0)
            );

            if (batchProducts != null && !batchProducts.isEmpty()) {
                // 收集有效的 productId（过滤 null）
                Set<Long> productIds = batchProducts.stream()
                        .map(ProcessSheetBatchProduct::getProductId)
                        .filter(Objects::nonNull)  // ← 添加 null 过滤
                        .collect(Collectors.toSet());

                // 查询产品详情（只查询一次）
                Map<Long, ProcessSheetProduct> productMap = new HashMap<>();
                if (!productIds.isEmpty()) {
                    List<ProcessSheetProduct> products = processSheetProductMapper.selectBatchIds(new ArrayList<>(productIds));
                    if (products != null && !products.isEmpty()) {
                        productMap = products.stream()
                                .collect(Collectors.toMap(ProcessSheetProduct::getId, p -> p));
                    }
                }

                // 组装数据
                final Map<Long, ProcessSheetProduct> finalProductMap = productMap;
                List<ProcessBatchDetailVO.BatchProductDetail> details = batchProducts.stream()
                        .map(bp -> {
                            ProcessBatchDetailVO.BatchProductDetail detail = new ProcessBatchDetailVO.BatchProductDetail();
                            detail.setProductCode(bp.getProductCode());
                            detail.setProductName(bp.getProductName());
                            detail.setProductionQuantity(bp.getProductionQuantity());

                            // 补充产品详情
                            if (bp.getProductId() != null) {
                                ProcessSheetProduct p = finalProductMap.get(bp.getProductId());
                                if (p != null) {
                                    detail.setSpecification(p.getSpecification());
                                    detail.setProductHeight(p.getProductHeight());
                                    detail.setUnitWeight(p.getUnitWeight());
                                } else {
                                    log.warn("⚠️ 未找到产品详情: productId={}, productCode={}",
                                            bp.getProductId(), bp.getProductCode());
                                }
                            } else {
                                log.warn("⚠️ 批次产品的 product_id 为 null: productCode={}, productName={}",
                                        bp.getProductCode(), bp.getProductName());
                            }

                            return detail;
                        })
                        .collect(Collectors.toList());

                vo.setProducts(details);
            }
        } else {
            log.warn("⚠️ 批次的 batch_plan_id 为 null，无法查询产品明细: batchId={}", id);
        }

        return vo;
    }

    @Override
    public Map<String, Object> getByFlowNumber(String flowNumber) {
        if (StringUtils.isBlank(flowNumber)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "流程卡号不能为空");
        }

        // 1. 根据流程卡号查询批次
        LambdaQueryWrapper<ProcessBatch> qw = new LambdaQueryWrapper<>();
        qw.eq(ProcessBatch::getFlowNumber, flowNumber)
                .eq(ProcessBatch::getIsDeleted, 0)
                .last("LIMIT 1");

        ProcessBatch batch = processBatchMapper.selectOne(qw);

        if (batch == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "未找到该流程卡号对应的生产批次");
        }

        Map<String, Object> result = new HashMap<>();
        result.put("batchId", batch.getId());
        result.put("batchCode", batch.getBatchCode());
        result.put("flowNumber", batch.getFlowNumber());
        result.put("deviceName", batch.getDeviceName());
        result.put("deviceCode", batch.getDeviceCode());

        // 2. 查询工艺单获取材料牌号
        String materialBrand = null;
        if (batch.getProcessSheetId() != null) {
            ProcessSheet sheet = processSheetMapper.selectById(batch.getProcessSheetId());
            if (sheet != null) {
                materialBrand = sheet.getMaterialBrand();
                result.put("materialBrand", materialBrand);
                result.put("materialSpec", sheet.getMaterialSpec());
                result.put("powderSupplier", sheet.getPowderSupplier());
            }
        }

        // 3. 查询产品列表
        List<Map<String, Object>> products = new ArrayList<>();

        if (batch.getBatchPlanId() != null) {
            // 通过 batchPlanId 查询批次产品分配表
            List<ProcessSheetBatchProduct> batchProducts = processSheetBatchProductMapper.selectList(
                    new LambdaQueryWrapper<ProcessSheetBatchProduct>()
                            .eq(ProcessSheetBatchProduct::getBatchId, batch.getBatchPlanId())
                            .eq(ProcessSheetBatchProduct::getIsDeleted, 0)
            );

            if (batchProducts != null && !batchProducts.isEmpty()) {
                for (ProcessSheetBatchProduct bp : batchProducts) {
                    Map<String, Object> productInfo = new HashMap<>();
                    productInfo.put("productCode", bp.getProductCode());
                    productInfo.put("productName", bp.getProductName());
                    productInfo.put("productionQuantity", bp.getProductionQuantity());
                    productInfo.put("materialBrand", materialBrand); // 每个产品都带上材料牌号
                    products.add(productInfo);
                }
            }
        }

        // 如果没有从批次产品表查到，降级使用批次主表的产品信息
        if (products.isEmpty() && StringUtils.isNotBlank(batch.getProductName())) {
            Map<String, Object> productInfo = new HashMap<>();
            productInfo.put("productCode", null);
            productInfo.put("productName", batch.getProductName());
            productInfo.put("productionQuantity", batch.getQuantity());
            productInfo.put("materialBrand", materialBrand);
            products.add(productInfo);
        }

        result.put("products", products);

        log.info("根据流程卡号查询批次信息: flowNumber={}, batchId={}, 产品数量={}",
                flowNumber, batch.getId(), products.size());

        return result;
    }

    @Override
    public List<PmcGroupedBatchesVO> listBatchesGrouped(Long processSheetId, String orderNumber, String productName, String status) {
        // 查询批次列表
        List<ProcessBatch> batches = this.listBatches(null, processSheetId, orderNumber, productName, status);

        if (batches == null || batches.isEmpty()) {
            return Collections.emptyList();
        }

        final Map<Long, WorkflowTask> parentTaskMap;
        final Map<Long, WorkflowTask> subTaskMap;
        final Map<Long, SalesOrder> orderMap;
        final Map<Long, List<ProcessSheetBatchProduct>> batchProductsMap;

        // 收集订单ID
        Set<Long> orderIds = batches.stream()
                .filter(b -> b.getSourceType() != null && b.getSourceType() == 1 && b.getSourceId() != null)
                .map(ProcessBatch::getSourceId)
                .collect(Collectors.toSet());

        // 查询父任务（CREATE_SCHEDULE）
        if (!orderIds.isEmpty()) {
            List<WorkflowTask> parentTasks = workflowTaskMapper.selectList(
                    new LambdaQueryWrapper<WorkflowTask>()
                            .eq(WorkflowTask::getTaskType, "CREATE_SCHEDULE")
                            .eq(WorkflowTask::getBizType, "SALES_ORDER")
                            .in(WorkflowTask::getBizId, orderIds)
                            .eq(WorkflowTask::getIsDelete, 0)
                            .isNull(WorkflowTask::getParentTaskId)
            );

            if (parentTasks != null && !parentTasks.isEmpty()) {
                parentTaskMap = parentTasks.stream()
                        .collect(Collectors.toMap(WorkflowTask::getBizId, t -> t));
            } else {
                parentTaskMap = Collections.emptyMap();
            }
        } else {
            parentTaskMap = Collections.emptyMap();
        }

        // 收集批次ID
        Set<Long> batchIds = batches.stream()
                .map(ProcessBatch::getId)
                .collect(Collectors.toSet());

        // 查询子任务（PUBLISH_BATCH）
        if (!batchIds.isEmpty()) {
            List<WorkflowTask> subTasks = workflowTaskMapper.selectList(
                    new LambdaQueryWrapper<WorkflowTask>()
                            .eq(WorkflowTask::getTaskType, "PUBLISH_BATCH")
                            .eq(WorkflowTask::getBizType, "PROCESS_BATCH")
                            .in(WorkflowTask::getBizId, batchIds)
                            .eq(WorkflowTask::getIsDelete, 0)
            );

            if (subTasks != null && !subTasks.isEmpty()) {
                subTaskMap = subTasks.stream()
                        .collect(Collectors.toMap(WorkflowTask::getBizId, t -> t));
            } else {
                subTaskMap = Collections.emptyMap();
            }
        } else {
            subTaskMap = Collections.emptyMap();
        }

        // 查询销售订单
        if (!orderIds.isEmpty()) {
            List<SalesOrder> orders = salesOrderMapper.selectBatchIds(new ArrayList<>(orderIds));
            if (orders != null && !orders.isEmpty()) {
                orderMap = orders.stream()
                        .collect(Collectors.toMap(SalesOrder::getId, o -> o));
            } else {
                orderMap = Collections.emptyMap();
            }
        } else {
            orderMap = Collections.emptyMap();
        }

        // 查询批次产品分配数据
        if (!batchIds.isEmpty()) {
            Set<Long> batchPlanIds = batches.stream()
                    .filter(b -> b.getBatchPlanId() != null)
                    .map(ProcessBatch::getBatchPlanId)
                    .collect(Collectors.toSet());

            if (!batchPlanIds.isEmpty()) {
                List<ProcessSheetBatchProduct> allProducts = processSheetBatchProductMapper.selectList(
                        new LambdaQueryWrapper<ProcessSheetBatchProduct>()
                                .in(ProcessSheetBatchProduct::getBatchId, batchPlanIds)
                                .eq(ProcessSheetBatchProduct::getIsDeleted, 0)
                );

                if (allProducts != null && !allProducts.isEmpty()) {
                    batchProductsMap = allProducts.stream()
                            .collect(Collectors.groupingBy(ProcessSheetBatchProduct::getBatchId));
                } else {
                    batchProductsMap = Collections.emptyMap();
                }
            } else {
                batchProductsMap = Collections.emptyMap();
            }
        } else {
            batchProductsMap = Collections.emptyMap();
        }
        // ==========查询产品详情 ==========
        final Map<Long, ProcessSheetProduct> productDetailMap;
        if (!batchProductsMap.isEmpty()) {
            Set<Long> productIds = batchProductsMap.values().stream()
                    .flatMap(List::stream)
                    .map(ProcessSheetBatchProduct::getProductId)
                    .filter(java.util.Objects::nonNull)
                    .collect(Collectors.toSet());

            if (!productIds.isEmpty()) {
                List<ProcessSheetProduct> products = processSheetProductMapper.selectBatchIds(new ArrayList<>(productIds));
                productDetailMap = (products != null && !products.isEmpty())
                        ? products.stream().collect(Collectors.toMap(ProcessSheetProduct::getId, p -> p))
                        : Collections.emptyMap();
            } else {
                productDetailMap = Collections.emptyMap();
            }
        } else {
            productDetailMap = Collections.emptyMap();
        }
        // 按订单号+产品名称分组
        Map<String, List<ProcessBatch>> grouped = batches.stream()
                .collect(Collectors.groupingBy(b ->
                        (b.getOrderNumber() == null ? "" : b.getOrderNumber()) + "||" +
                                (b.getProductName() == null ? "" : b.getProductName())
                ));

        // 组装VO
        List<PmcGroupedBatchesVO> result = new ArrayList<>();
        for (Map.Entry<String, List<ProcessBatch>> entry : grouped.entrySet()) {
            String key = entry.getKey();
            String[] parts = key.split("\\Q||\\E");
            String on = parts.length > 0 ? parts[0] : "";
            String pn = parts.length > 1 ? parts[1] : "";
            List<ProcessBatch> list = entry.getValue();

            // 计算数量总和
            int qtySum = list.stream()
                    .map(b -> b.getQuantity() == null ? 0 : b.getQuantity())
                    .reduce(0, Integer::sum);

            // 查询订单和父任务信息
            SalesOrder so = null;
            WorkflowTask parentTask = null;
            for (ProcessBatch b : list) {
                if (b.getSourceType() != null && b.getSourceType() == 1 && b.getSourceId() != null) {
                    so = orderMap.get(b.getSourceId());
                    parentTask = parentTaskMap.get(b.getSourceId());
                    if (so != null) break;
                }
            }

            PmcGroupedBatchesVO vo = new PmcGroupedBatchesVO();
            vo.setOrderNumber(on);
            vo.setProductName(pn);
            vo.setQuantitySum(qtySum);

            // 填充父任务信息
            if (parentTask != null) {
                vo.setParentTaskId(parentTask.getId());
                vo.setParentTaskTitle(parentTask.getTaskTitle());
                vo.setParentTaskStatus(parentTask.getTaskStatus());
                vo.setParentTaskClaimedBy(parentTask.getClaimedBy());
                vo.setParentTaskClaimedByName(parentTask.getClaimedByName());
                vo.setParentTaskClaimedTime(parentTask.getClaimedTime());
            }

            // 填充订单信息
            if (so != null) {
                if (so.getDueDate() != null) {
                    vo.setDueDate(so.getDueDate().toInstant()
                            .atZone(ZoneId.systemDefault())
                            .toLocalDateTime());
                }
                vo.setCreatedByName(so.getCreatedByName());
                if (so.getCreateTime() != null) {
                    vo.setCreateTime(so.getCreateTime().toInstant()
                            .atZone(ZoneId.systemDefault())
                            .toLocalDateTime());
                }
            }

            // 组装批次列表
            List<PmcGroupedBatchesVO.BatchItem> items = new ArrayList<>();
            list.stream()
                    .sorted(Comparator.comparing(
                            b -> b.getBatchCode() != null ? b.getBatchCode() : "",
                            Collator.getInstance(Locale.CHINA)
                    ))
                    .forEach(b -> {
                        WorkflowTask subTask = subTaskMap.get(b.getId());

                        PmcGroupedBatchesVO.BatchItem it = new PmcGroupedBatchesVO.BatchItem();
                        it.setFlowNumber(b.getFlowNumber());
                        it.setProductionPlanNumber(b.getProductionPlanNumber());
                        it.setId(b.getId());
                        it.setBatchCode(b.getBatchCode() != null ? b.getBatchCode() : "待生成");
                        it.setStatus(b.getStatus());
                        it.setPlanStart(b.getPlanStart());
                        it.setPlanEnd(b.getPlanEnd());
                        it.setPublishTime(b.getPublishTime());
                        it.setQuantity(b.getQuantity());

                        // 填充设备名称
                        it.setDeviceName(b.getDeviceName());

                        // 填充子任务信息
                        if (subTask != null) {
                            it.setSubTaskId(subTask.getId());
                            it.setSubTaskStatus(subTask.getTaskStatus());
                            it.setSubTaskClaimedBy(subTask.getClaimedBy());
                            it.setSubTaskClaimedByName(subTask.getClaimedByName());
                        }

                        // ✅ 填充产品列表（增强版）
                        if (b.getBatchPlanId() != null) {
                            List<ProcessSheetBatchProduct> batchProducts = batchProductsMap.get(b.getBatchPlanId());

                            if (batchProducts != null && !batchProducts.isEmpty()) {
                                List<PmcGroupedBatchesVO.ProductInfo> productInfos = batchProducts.stream()
                                        .map(bp -> {
                                            PmcGroupedBatchesVO.ProductInfo info = new PmcGroupedBatchesVO.ProductInfo();
                                            info.setProductCode(bp.getProductCode());
                                            info.setProductName(bp.getProductName());
                                            info.setQuantity(bp.getProductionQuantity());

                                            // ✅ 新增：从产品详情补充字段
                                            ProcessSheetProduct detail = productDetailMap.get(bp.getProductId());
                                            if (detail != null) {
                                                info.setSpecification(detail.getSpecification());
                                                info.setProductHeight(detail.getProductHeight());
                                                info.setUnitWeight(detail.getUnitWeight());
                                            }
                                            return info;
                                        })
                                        .collect(Collectors.toList());

                                it.setProducts(productInfos);
                            }
                        }

                        items.add(it);
                    });

            vo.setBatches(items);
            result.add(vo);
        }

        // 排序
        result.sort(Comparator
                .<PmcGroupedBatchesVO, String>comparing(
                        vo -> vo.getOrderNumber() != null ? vo.getOrderNumber() : "",
                        Collator.getInstance(Locale.CHINA)
                )
                .thenComparing(
                        vo -> vo.getProductName() != null ? vo.getProductName() : "",
                        Collator.getInstance(Locale.CHINA)
                )
        );

        return result;
    }

    @Override
    public ProcessBatch getById(Long id) {
        return processBatchMapper.selectById(id);
    }

    @Override
    public List<ProcessBatchStep> listSteps(Long batchId) {
        List<ProcessBatchStep> steps = processBatchStepMapper.selectList(new LambdaQueryWrapper<ProcessBatchStep>().eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0).orderByAsc(ProcessBatchStep::getSequenceNo));

        // 添加调试日志，帮助排查前端显示问题
        if (steps != null && !steps.isEmpty()) {
            boolean needFill = steps.stream().anyMatch(step ->
                    (step.getProcessRequirement() == null || step.getInspectionMethod() == null || step.getInspectionCriteria() == null)
                            && step.getProcessSheetStepId() != null);
            if (needFill) {
                Set<Long> templateIds = steps.stream()
                        .map(ProcessBatchStep::getProcessSheetStepId)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toSet());
                if (!templateIds.isEmpty()) {
                    List<ProcessSheetStep> templateSteps = processSheetStepMapper.selectBatchIds(new ArrayList<>(templateIds));
                    Map<Long, ProcessSheetStep> templateMap = templateSteps.stream().collect(Collectors.toMap(ProcessSheetStep::getId, t -> t));
                    for (ProcessBatchStep step : steps) {
                        ProcessSheetStep template = templateMap.get(step.getProcessSheetStepId());
                        if (template == null) {
                            continue;
                        }
                        if (step.getProcessRequirement() == null) {
                            step.setProcessRequirement(template.getProcessRequirement());
                        }
                        if (step.getInspectionMethod() == null) {
                            step.setInspectionMethod(template.getInspectionMethod());
                        }
                        if (step.getInspectionCriteria() == null) {
                            step.setInspectionCriteria(template.getInspectionCriteria());
                        }
                    }
                }
            }
            log.info("查询批次{}的工序列表，共{}个工序", batchId, steps.size());
            for (ProcessBatchStep step : steps) {
                log.info("工序详情 - ID:{}, sequenceNo:{}, 工序名:{}, 计划时长:{}分钟, 计划开始:{}, 计划结束:{}", step.getId(), step.getSequenceNo(), step.getOperationName(), step.getExpectedDurationMinutes(), step.getPlanStart(), step.getPlanEnd());
            }
        } else {
            log.warn("批次{}没有找到工序数据", batchId);
        }

        return steps;
    }

    @Override
    public List<BatchScheduleHistory> listScheduleHistory(Long batchId) {
        if (batchId == null || batchId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不合法");
        }
        return batchScheduleHistoryMapper.selectList(new LambdaQueryWrapper<BatchScheduleHistory>().eq(BatchScheduleHistory::getBatchId, batchId).orderByDesc(BatchScheduleHistory::getChangedAt));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean markStepStarted(Long batchId, Long stepId) {
        if (batchId == null || stepId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "batchId/stepId不能为空");
        }
        ProcessBatchStep step = processBatchStepMapper.selectById(stepId);
        if (step == null || (step.getIsDeleted() != null && step.getIsDeleted() == 1) || !Objects.equals(step.getBatchId(), batchId)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "工序不存在");
        }
        if (step.getActualStart() == null) {
            LocalDateTime now = LocalDateTime.now();
            UpdateWrapper<ProcessBatchStep> uw = new UpdateWrapper<>();
            uw.eq("id", stepId).set("actual_start", now).set("status", "IN_PROGRESS").set("update_time", now);
            processBatchStepMapper.update(null, uw);
        }
        return true;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean markStepCompleted(Long batchId, Long stepId) {
        if (batchId == null || stepId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "batchId/stepId不能为空");
        }

        ProcessBatchStep step = processBatchStepMapper.selectById(stepId);
        if (step == null || (step.getIsDeleted() != null && step.getIsDeleted() == 1) || !Objects.equals(step.getBatchId(), batchId)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "工序不存在");
        }

        // 幂等性检查：如果已经完成或待质检，直接返回true
        if ("COMPLETED".equals(step.getStatus()) || "PENDING_QA".equals(step.getStatus())) {
            return true;
        }

        // 【修改】判断是否需要质检
        boolean isInspectionRequired = step.getNeedInspection() != null && step.getNeedInspection() == 1;

        // 确定下一状态：需检则为待质检，否则为已完成
        String nextStatus = isInspectionRequired ? "PENDING_QA" : "COMPLETED";

        LocalDateTime now = LocalDateTime.now();
        UpdateWrapper<ProcessBatchStep> uw = new UpdateWrapper<>();
        uw.eq("id", stepId).set("actual_end", now).set("status", nextStatus).set("update_time", now);
        processBatchStepMapper.update(null, uw);

        // 刷新内存对象状态
        step.setStatus(nextStatus);

        // 【修改】触发后续流程
        if (isInspectionRequired) {
            // 创建质检任务
            ProcessBatch batch = processBatchMapper.selectById(batchId);
            String orderNumber = batch != null ? batch.getOrderNumber() : "";

            // creatorId 暂传 null 或当前登录用户（此处上下文可能无法直接获取 User 对象，通常由 Controller 传入，这里为简化逻辑暂传 null）
            // 建议：如果 Service 方法签名无法变更，就在这里传 null，Task Service 会处理
            workflowTaskCreationService.createQualityInspectionTask(stepId, batchId, orderNumber, step.getOperationName(), step.getSequenceNo(), null, "系统自动触发");

            log.info("工序需检，已创建质检任务: stepId={}, stepName={}", stepId, step.getOperationName());
        } else {
            // 不需要质检，直接检查批次是否全部完成
            updateBatchStatusIfCompleted(batchId);
        }

        return true;
    }

    @Override
    public PMCTaskDetailVO getPmcTaskDetail(Long taskId, Long userId) {
        if (taskId == null || taskId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "任务ID不能为空");
        }

        PMCTaskDetailVO vo = new PMCTaskDetailVO();

        // ========== 1. 查询任务基本信息 ==========
        WorkflowTask task = workflowTaskMapper.selectById(taskId);
        if (task == null || task.getIsDelete() == 1) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "任务不存在");
        }

        // 填充任务信息
        vo.setTaskId(String.valueOf(taskId));
        vo.setTaskTitle(task.getTaskTitle());
        vo.setTaskDescription(task.getTaskDescription());
        vo.setTaskType(task.getTaskType());
        vo.setTaskStatus(task.getTaskStatus());
        vo.setExpectedCompleteTime(task.getExpectedCompleteTime());
        vo.setClaimedByName(task.getClaimedByName());
        vo.setClaimedTime(task.getClaimedTime());
        vo.setTaskCreateTime(task.getCreateTime());

        // ========== 2. 解析业务ID ==========
        Long orderId = null;
        Long processSheetId = null;
        Long batchId = null;

        // 情况 A: 父任务 (排程总任务)，bizId = 订单ID
        if ("CREATE_SCHEDULE".equals(task.getTaskType()) && "SALES_ORDER".equals(task.getBizType())) {
            orderId = task.getBizId();
        }
        // 情况 B: 子任务 (批次发布任务)，bizId = 批次ID
        else if ("PUBLISH_BATCH".equals(task.getTaskType())) {
            batchId = task.getBizId();

            // 查询批次
            ProcessBatch batch = processBatchMapper.selectById(batchId);

            if (batch == null) {
                log.error("❌ 批次不存在: taskId={}, batchId={}", taskId, batchId);
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "批次不存在（ID：" + batchId + "），请联系工程部确认");
            }

            processSheetId = batch.getProcessSheetId();

            // 通过 Batch -> ProcessSheet -> SourceId 找到 OrderId
            if (processSheetId != null) {
                ProcessSheet sheet = processSheetMapper.selectById(processSheetId);
                if (sheet != null) {
                    orderId = sheet.getSourceId();
                    vo.setOrderType(sheet.getSourceType() != null && sheet.getSourceType() == 1 ? "SALES" : "RDO");
                }
            }

            // 预填批次相关信息
            vo.setMaterialPowder(batch.getPowderModel());

            // ✅ 查询批次的产品分配（process_sheet_batch_product）
            if (batch.getBatchPlanId() != null) {
                List<ProcessSheetBatchProduct> batchProducts = processSheetBatchProductMapper.selectList(new LambdaQueryWrapper<ProcessSheetBatchProduct>().eq(ProcessSheetBatchProduct::getBatchId, batch.getBatchPlanId()).eq(ProcessSheetBatchProduct::getIsDeleted, 0));

                if (batchProducts != null && !batchProducts.isEmpty()) {
                    List<PMCTaskDetailVO.BatchProductVO> batchProductVOs = batchProducts.stream().map(bp -> {
                        PMCTaskDetailVO.BatchProductVO bpVO = new PMCTaskDetailVO.BatchProductVO();
                        bpVO.setId(String.valueOf(bp.getId()));
                        bpVO.setBatchId(String.valueOf(bp.getBatchId()));
                        bpVO.setProcessSheetId(String.valueOf(bp.getProcessSheetId()));
                        bpVO.setProductId(String.valueOf(bp.getProductId()));
                        bpVO.setProductCode(bp.getProductCode());
                        bpVO.setProductName(bp.getProductName());
                        bpVO.setProductionQuantity(bp.getProductionQuantity());
                        bpVO.setCreateTime(bp.getCreateTime());
                        bpVO.setUpdateTime(bp.getUpdateTime());
                        return bpVO;
                    }).collect(Collectors.toList());

                    vo.setBatchProducts(batchProductVOs);

                    log.info("✅ 查询到批次产品分配: batchId={}, 产品数量={}", batchId, batchProductVOs.size());
                }
            }
        }
        // 其他不支持的类型
        else {
            log.info("非PMC排期/发布任务，仅返回基本信息: taskType={}, bizType={}", task.getTaskType(), task.getBizType());
            return vo;
        }

        vo.setOrderId(orderId);
        vo.setOrderNumber(task.getBizNumber());

        // ========== 3. 查询订单信息 ==========
        if (orderId != null) {
            try {
                // 尝试查询销售订单
                SalesOrder salesOrder = salesOrderMapper.selectById(orderId);
                if (salesOrder != null) {
                    vo.setOrderType("SALES");
                    vo.setOrderNumber(salesOrder.getOrderNumber());
                    vo.setCustomerName(salesOrder.getCustomerName());
                    if (salesOrder.getDueDate() != null) {
                        vo.setDueDate(salesOrder.getDueDate().toInstant()
                                .atZone(ZoneId.systemDefault())
                                .toLocalDateTime());
                    }
                    vo.setUrgencyLevel(salesOrder.getUrgencyLevel());
                    vo.setQualityLevel(salesOrder.getQualityLevel());
                    vo.setSpecialRequirement(salesOrder.getSpecialRequirement());
                    vo.setOrderStatus(salesOrder.getOrderStatus());
                    if (salesOrder.getCreateTime() != null) {
                        vo.setOrderCreateTime(salesOrder.getCreateTime().toInstant()
                                .atZone(ZoneId.systemDefault())
                                .toLocalDateTime());
                    }

                    // ✅ 填充第一个产品信息作为默认显示（兼容老逻辑）
                    List<SalesOrderItem> orderItems = salesOrderItemMapper.selectList(new LambdaQueryWrapper<SalesOrderItem>().eq(SalesOrderItem::getOrderId, orderId).eq(SalesOrderItem::getIsDeleted, 0).orderByAsc(SalesOrderItem::getSortOrder).last("LIMIT 1"));

                    if (orderItems != null && !orderItems.isEmpty()) {
                        SalesOrderItem firstItem = orderItems.get(0);
                        vo.setProductCode(firstItem.getProductCode());
                        vo.setProductName(firstItem.getProductName());
                        vo.setSpecification(firstItem.getSpecification());
                        vo.setQuantity(firstItem.getQuantity());
                    }
                } else {
                    // RDO订单
                    log.info("未查询到销售订单，可能为RDO订单: orderId={}", orderId);
                }
            } catch (Exception e) {
                log.warn("查询订单信息失败: orderId={}", orderId, e);
            }
        }

        // ========== 4. 查询工艺单 ==========
        ProcessSheet sheet = null;
        if (processSheetId != null) {
            sheet = processSheetMapper.selectById(processSheetId);
        } else if (orderId != null) {
            // 降级逻辑：通过 OrderId 反查最新的工艺单
            sheet = processSheetMapper.selectOne(new LambdaQueryWrapper<ProcessSheet>().eq(ProcessSheet::getSourceType, 1).eq(ProcessSheet::getSourceId, orderId).eq(ProcessSheet::getIsDeleted, 0).orderByDesc(ProcessSheet::getCreateTime).last("LIMIT 1"));
        }

        if (sheet == null) {
            log.warn("未找到工艺单信息: processSheetId={}, orderId={}", processSheetId, orderId);
            return vo;
        }

        // 填充工艺单信息
        vo.setProcessSheetId(sheet.getId());
        vo.setProcessSheetNo(batchCodeGeneratorService.buildFullProcessSheetNo(sheet.getProcessSheetNo(), sheet.getProcessVersion()));
        vo.setMaterialPowder(sheet.getMaterialBrand());
        vo.setMaterialBrand(sheet.getMaterialBrand());
        vo.setMaterialSpec(sheet.getMaterialSpec());
        vo.setPowderSupplier(sheet.getPowderSupplier());
        vo.setMachineModel(sheet.getDeviceName());
        vo.setProcessSheetCreatedByName(sheet.getCreatedByName());
        vo.setTechnicalRequirements(sheet.getTechnicalRequirements());
        vo.setProcessFileIds(sheet.getProcessFileIds());

        // ✅ 如果是RDO订单，从工艺单补充基本产品信息（兼容老字段）
        if ("RDO".equals(vo.getOrderType()) && vo.getProductName() == null) {
            vo.setProductName(sheet.getProductName());
            vo.setProductCode(sheet.getProductCode());
            vo.setSpecification(sheet.getSpecification());
            vo.setQuantity(sheet.getQuantity());
        }

        // ========== 5. ✅ 查询工艺单产品明细列表（核心功能）==========
        try {
            List<ProcessSheetProduct> products = processSheetProductMapper.selectList(new LambdaQueryWrapper<ProcessSheetProduct>().eq(ProcessSheetProduct::getProcessSheetId, sheet.getId()).eq(ProcessSheetProduct::getIsDeleted, 0).orderByAsc(ProcessSheetProduct::getSortOrder));

            if (products != null && !products.isEmpty()) {
                List<PMCTaskDetailVO.ProductDetailVO> productVOs = products.stream().map(p -> {
                    PMCTaskDetailVO.ProductDetailVO pVO = new PMCTaskDetailVO.ProductDetailVO();
                    pVO.setId(String.valueOf(p.getId()));
                    pVO.setProcessSheetId(String.valueOf(p.getProcessSheetId()));
                    pVO.setOrderItemId(p.getOrderItemId() != null ? String.valueOf(p.getOrderItemId()) : null);
                    pVO.setProductCode(p.getProductCode());
                    pVO.setProductName(p.getProductName());
                    pVO.setSpecification(p.getSpecification());
                    pVO.setQuantity(p.getQuantity());
                    pVO.setPartDrawingNo(p.getPartDrawingNo());
                    pVO.setUnitWeight(p.getUnitWeight());
                    pVO.setProductHeight(p.getProductHeight());
                    pVO.setSortOrder(p.getSortOrder());
                    pVO.setRemark(p.getRemark());
                    pVO.setCreateTime(p.getCreateTime());
                    pVO.setUpdateTime(p.getUpdateTime());
                    return pVO;
                }).collect(Collectors.toList());

                vo.setProducts(productVOs);

                log.info("✅ 查询到工艺单产品明细: processSheetId={}, 产品数量={}", sheet.getId(), productVOs.size());

                // ✅ 如果老字段为空，用第一个产品填充（兼容性）
                if (vo.getProductName() == null && !productVOs.isEmpty()) {
                    PMCTaskDetailVO.ProductDetailVO firstProduct = productVOs.get(0);
                    vo.setProductCode(firstProduct.getProductCode());
                    vo.setProductName(firstProduct.getProductName());
                    vo.setSpecification(firstProduct.getSpecification());
                    vo.setQuantity(firstProduct.getQuantity());
                }
            } else {
                log.warn("⚠️ 工艺单无产品明细: processSheetId={}", sheet.getId());
            }
        } catch (Exception e) {
            log.error("❌ 查询工艺单产品明细失败: processSheetId={}", sheet.getId(), e);
        }

        // ========== 6. 查询工序列表 ==========
        try {
            List<ProcessSheetStep> steps = processSheetStepMapper.selectList(new LambdaQueryWrapper<ProcessSheetStep>().eq(ProcessSheetStep::getProcessSheetId, sheet.getId()).eq(ProcessSheetStep::getIsDeleted, 0).orderByAsc(ProcessSheetStep::getSequenceNo));

            if (steps != null && !steps.isEmpty()) {
                List<PMCTaskDetailVO.ProcessSheetStepVO> stepVOs = steps.stream().map(s -> {
                    PMCTaskDetailVO.ProcessSheetStepVO stepVO = new PMCTaskDetailVO.ProcessSheetStepVO();
                    stepVO.setSequenceNo(s.getSequenceNo());
                    stepVO.setOperationName(s.getOperationName());
                    stepVO.setIsInspectionRequired(s.getIsInspectionRequired());
                    stepVO.setIsOutsourced(s.getIsOutsourced());
                    return stepVO;
                }).collect(Collectors.toList());
                vo.setSteps(stepVOs);

                log.info("✅ 查询到工序步骤: processSheetId={}, 工序数量={}", sheet.getId(), stepVOs.size());
            }
        } catch (Exception e) {
            log.error("❌ 查询工序步骤失败: processSheetId={}", sheet.getId(), e);
        }

        return vo;
    }

    /**
     * ✅ 新增：获取批次详细进度（包含质检状态）
     *
     * @param batchId 批次ID
     * @return 批次进度VO
     */
    @Override
    public ProcessBatchProgressVO getBatchProgress(Long batchId) {
        if (batchId == null || batchId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "批次ID不能为空");
        }

        // 1. 查询批次基本信息
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "批次不存在");
        }

        // 2. 查询所有工序
        LambdaQueryWrapper<ProcessBatchStep> qw = new LambdaQueryWrapper<>();
        qw.eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0).orderByAsc(ProcessBatchStep::getSequenceNo);
        List<ProcessBatchStep> steps = processBatchStepMapper.selectList(qw);

        if (steps == null || steps.isEmpty()) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "批次无工序数据");
        }

        // 3. 统计进度
        int totalSteps = steps.size();
        int completedSteps = 0;
        ProcessBatchStep currentStep = null;
        ProcessBatchStep nextStep = null;
        String qaStatus = "NONE"; // NONE, IN_PROGRESS, WAITING
        String currentStepName = null;
        Integer currentStepNo = null;

        for (int i = 0; i < steps.size(); i++) {
            ProcessBatchStep step = steps.get(i);

            // 统计已完成工序
            if ("COMPLETED".equals(step.getStatus())) {
                completedSteps++;
            }

            // 查找当前工序（第一个未完成的）
            if (currentStep == null && !"COMPLETED".equals(step.getStatus())) {
                currentStep = step;
                currentStepName = step.getOperationName();
                currentStepNo = step.getSequenceNo();

                // 判断质检状态
                if ("PENDING_QA".equals(step.getStatus())) {
                    qaStatus = "WAITING"; // 待质检
                } else if ("IN_PROGRESS".equals(step.getStatus()) && step.getNeedInspection() == 1) {
                    qaStatus = "IN_PROGRESS"; // 可能即将进入质检
                }

                // 查找下一道工序
                if (i + 1 < steps.size()) {
                    nextStep = steps.get(i + 1);
                }
            }
        }

        // 4. 计算进度百分比
        int progressPercent = (totalSteps > 0) ? (completedSteps * 100 / totalSteps) : 0;

        // 5. 查询清机记录
        boolean cleaningCompleted = false;
        LambdaQueryWrapper<com.yupi.springbootinit.model.entity.ProductionOperationRecord> cleanQw = new LambdaQueryWrapper<>();
        cleanQw.eq(com.yupi.springbootinit.model.entity.ProductionOperationRecord::getBatchId, batchId).eq(com.yupi.springbootinit.model.entity.ProductionOperationRecord::getExecType, "PREP_CLEANING").eq(com.yupi.springbootinit.model.entity.ProductionOperationRecord::getEventType, "COMPLETE").eq(com.yupi.springbootinit.model.entity.ProductionOperationRecord::getIsDelete, 0);
        Long cleanCount = productionOperationRecordMapper.selectCount(cleanQw);
        cleaningCompleted = (cleanCount != null && cleanCount > 0);

        // 6. 组装VO
        ProcessBatchProgressVO vo = new ProcessBatchProgressVO();
        vo.setBatchId(batchId);
        vo.setBatchCode(batch.getBatchCode());
        vo.setOrderNumber(batch.getOrderNumber());
        vo.setProductName(batch.getProductName());
        vo.setStatus(batch.getStatus());
        vo.setPlanStart(batch.getPlanStart());
        vo.setPlanEnd(batch.getPlanEnd());
        vo.setTotalSteps(totalSteps);
        vo.setCompletedSteps(completedSteps);
        vo.setProgressPercent(progressPercent);
        vo.setCleaningCompleted(cleaningCompleted);
        vo.setCurrentStepNo(currentStepNo);
        vo.setCurrentStepName(currentStepName);
        vo.setCurrentStepStatus(currentStep != null ? currentStep.getStatus() : null);
        vo.setQaStatus(qaStatus);
        vo.setNextStepName(nextStep != null ? nextStep.getOperationName() : null);

        return vo;
    }

    /**
     * 检查并更新批次状态为 COMPLETED（如果所有工序都已完成）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchStatusIfCompleted(Long batchId) {
        if (batchId == null || batchId <= 0) {
            log.warn("updateBatchStatusIfCompleted: batchId 无效, batchId={}", batchId);
            return false;
        }

        // 1. 查询批次信息
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            log.warn("updateBatchStatusIfCompleted: 批次不存在, batchId={}", batchId);
            return false;
        }

        // 2. 如果已经是 COMPLETED 状态，直接返回
        if ("COMPLETED".equals(batch.getStatus())) {
            log.debug("批次已是完成状态，跳过更新: batchId={}", batchId);
            return true;
        }

        // 3. 查询该批次的所有工序
        LambdaQueryWrapper<ProcessBatchStep> qw = new LambdaQueryWrapper<>();
        qw.eq(ProcessBatchStep::getBatchId, batchId).eq(ProcessBatchStep::getIsDeleted, 0);
        List<ProcessBatchStep> steps = processBatchStepMapper.selectList(qw);

        if (steps == null || steps.isEmpty()) {
            log.warn("批次无工序数据，无法判断完成状态: batchId={}", batchId);
            return false;
        }

        // 4. 检查所有工序是否都已完成
        long completedCount = steps.stream().filter(s -> "COMPLETED".equals(s.getStatus())).count();

        boolean allCompleted = (completedCount == steps.size());

        log.info("批次工序完成情况: batchId={}, 总工序={}, 已完成={}, 全部完成={}", batchId, steps.size(), completedCount, allCompleted);

        // 5. 如果所有工序都完成，更新批次状态
        if (allCompleted) {
            LocalDateTime now = LocalDateTime.now();
            UpdateWrapper<ProcessBatch> uw = new UpdateWrapper<>();
            uw.eq("id", batchId).eq("is_deleted", 0).in("status", RELEASED, PENDING_QA).ne("status", COMPLETED).set("status", COMPLETED).set("update_time", now).setSql("version = version + 1");

            int rows = processBatchMapper.update(null, uw);

            if (rows > 0) {
                log.info("✅ 批次状态已更新为 COMPLETED: batchId={}, orderNumber={}, batchCode={}", batchId, batch.getOrderNumber(), batch.getBatchCode());
                return true;
            } else {
                log.debug("批次状态更新失败（可能已被其他线程更新）: batchId={}", batchId);
                return true; // 已经是 COMPLETED，也视为成功
            }
        } else {
            log.debug("批次仍有未完成工序，不更新状态: batchId={}, 未完成工序数={}", batchId, steps.size() - completedCount);
            return false;
        }
    }

    /**
     * 检查工艺单的所有批次是否都已完成
     */
    @Override
    public boolean isAllBatchesCompleted(Long processSheetId) {
        if (processSheetId == null || processSheetId <= 0) {
            log.warn("isAllBatchesCompleted: processSheetId 无效, processSheetId={}", processSheetId);
            return false;
        }

        // 1. 查询该工艺单的所有有效批次
        LambdaQueryWrapper<ProcessBatch> qw = new LambdaQueryWrapper<>();
        qw.eq(ProcessBatch::getProcessSheetId, processSheetId).eq(ProcessBatch::getIsDeleted, 0);
        List<ProcessBatch> batches = processBatchMapper.selectList(qw);

        if (batches == null || batches.isEmpty()) {
            log.warn("工艺单无批次数据: processSheetId={}", processSheetId);
            return false;
        }

        // 2. 检查每个批次的所有工序是否都完成（纯查询，无副作用）
        for (ProcessBatch batch : batches) {
            LambdaQueryWrapper<ProcessBatchStep> stepQw = new LambdaQueryWrapper<>();
            stepQw.eq(ProcessBatchStep::getBatchId, batch.getId()).eq(ProcessBatchStep::getIsDeleted, 0);
            List<ProcessBatchStep> steps = processBatchStepMapper.selectList(stepQw);

            if (steps == null || steps.isEmpty()) {
                log.warn("批次无工序数据: batchId={}, batchCode={}", batch.getId(), batch.getBatchCode());
                return false;
            }

            // 检查是否所有工序都完成
            boolean allStepsCompleted = steps.stream().allMatch(s -> COMPLETED.equals(s.getStatus()));

            if (!allStepsCompleted) {
                long incompleteCount = steps.stream().filter(s -> !COMPLETED.equals(s.getStatus())).count();

                log.info("批次未完成: batchId={}, batchCode={}, 总工序={}, 未完成工序={}", batch.getId(), batch.getBatchCode(), steps.size(), incompleteCount);
                return false;
            }
        }

        // 3. 所有批次的所有工序都完成
        log.info("✅ 工艺单的所有批次都已完成: processSheetId={}, 批次数={}", processSheetId, batches.size());
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessBatch scheduleBatch(Long batchId, LocalDateTime planStart, List<ProcessBatchStepDurationDTO> stepDurations, Long userId, String userName) {

        if (batchId == null || batchId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "批次ID不能为空");
        }

        if (planStart == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "计划开始时间不能为空");
        }

        log.info("========== 开始批次排期 ==========");
        log.info("batchId={}, planStart={}, userId={}, userName={}", batchId, planStart, userId, userName);

        // ========== 1. 验证批次存在性 ==========
        ProcessBatch batch = processBatchMapper.selectById(batchId);
        if (batch == null || (batch.getIsDeleted() != null && batch.getIsDeleted() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "批次不存在");
        }

        log.info("✅ 批次信息: batchCode={}, deviceName={}, status={}", batch.getBatchCode(), batch.getDeviceName(), batch.getStatus());

        // ========== 2. 状态校验：只能对待发布的批次排期 ==========
        if (!"WAITING_RELEASE".equals(batch.getStatus())) {
            // 如果已经排期过，允许重新排期（改期）
            log.warn("批次状态非待发布，当前状态: {}", batch.getStatus());

            // 可选：严格模式下不允许改期
            // throw new BusinessException(ErrorCode.OPERATION_ERROR,
            //     "批次状态不允许排期，当前状态：" + batch.getStatus());
        }

        // ========== 3. 如果提供了工序时长，先更新 ==========
        if (stepDurations != null && !stepDurations.isEmpty()) {
            log.info("更新工序时长: 共{}个工序", stepDurations.size());
            updateStepDurations(batchId, stepDurations, userId, userName);
        }

        // ========== 4. 调用自动排程方法 ==========
        ProcessBatch result = autoSchedule(batchId, planStart, stepDurations, userId, userName);

        log.info("========== 批次排期完成 ==========");
        log.info("batchId={}, batchCode={}, planStart={}, planEnd={}", result.getId(), result.getBatchCode(), result.getPlanStart(), result.getPlanEnd());

        return result;
    }

}
